#!/usr/bin/python2.5

"""
Specific App Engine features demonstrated include:
 * the Image API, which is used for creating thumbnails of uploaded cloths
 * the Datastore API, which is used to store both the metadata for and
   the actual content of cloths
 * the ListProperty type, which is used for efficient tagging support, and
 * support for file uploads using the WebOb request object

We use the webapp.py WSGI framework and the Django templating
language, both of which are documented in the App Engine docs
(http://appengine.google.com/docs/).
"""

import cgi
import os

from google.appengine.api import images
from google.appengine.api import users
from google.appengine.ext import db
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template

import wsgiref.handlers


# Makes the tags defined by templatetags/basetags.py usable
# by templates rendered in this file
template.register_template_library('templatetags.basetags')


class ClothCategory(db.Model):
  """An clothcategory is an organizational unit for cloths.

  Properties:
    name: sanitized, user entered name for the clothcategory
    creator: Google Account of the person who created the clothcategory
    created_date: DateTime the clothcategory was created
  """

  name = db.StringProperty()
  creator = db.UserProperty()
  created_date = db.DateTimeProperty(auto_now_add=True)


class Cloth(db.Model):
  """Storage for a cloth and its associated metadata.

  Properties:
    submitter: Google Account of the person who submitted the cloth
    submitted_date: DateTime the cloth was submitted
    title: sanitized, user entered title for the cloth
    caption: sanitized, user entered caption for the cloth
    clothcategory: reference to clothcategory the cloth is in
    tags: a StringListProperty of tags for the cloth
    data: data for the original cloth, converted into png format
    thumbnail_data: png format data for the thumbnail for this cloth
  """

  submitter = db.UserProperty()
  submitted_date = db.DateTimeProperty(auto_now_add=True)
  title = db.StringProperty()
  caption = db.StringProperty(multiline=True)
  clothcategory = db.ReferenceProperty(ClothCategory, collection_name='cloths')
  tags = db.StringListProperty()
  data = db.BlobProperty()
  thumbnail_data = db.BlobProperty()


class ImageSharingBaseHandler(webapp.RequestHandler):
  """Base Image Sharing RequestHandlers with some convenience functions."""

  def template_path(self, filename):
    """Returns the full path for a template from its path relative to here."""
    return os.path.join(os.path.dirname(__file__), filename)

  def render_to_response(self, filename, template_args):
    """Renders a Django template and sends it to the client.

    Args:
      filename: template path (relative to this file)
      template_args: argument dict for the template
    """
    template_args.setdefault('current_uri', self.request.uri)
    self.response.out.write(
        template.render(self.template_path(filename), template_args)
    )


class ImageSharingClothCategoryIndex(ImageSharingBaseHandler):
  """Handler for listing clothcategories."""

  def get(self):
    """Lists all available clothcategories."""
    clothcategories = ClothCategory.all().order('-created_date')
    self.render_to_response('index.html', {
        'clothcategories': clothcategories,
      })


class ImageSharingClothCategoryCreate(ImageSharingBaseHandler):
  """Handler for creating a new ClothCategory via form."""

  def get(self):
    """Displays the clothcategory creation form."""
    self.render_to_response('new.html', {})

  def post(self):
    """Processes an clothcategory creation request."""
    ClothCategory(name=cgi.escape(self.request.get('clothcategoryname')),
          creator=users.get_current_user()).put()
    self.redirect('/')

PICTURES_PER_ROW = 5

class ImageSharingClothCategoryView(ImageSharingBaseHandler):
  """Handler for viewing the cloths in a particular clothcategory."""

  def get(self, clothcategory_key):
    """Displays a single clothcategory.

    Note that in this and later handlers, the args come
    from a capturing group in the WSGIApplication specification.
    See the webapp framework docs for more info.

    Args:
      clothcategory_key: the datastore key for the ClothCategory to view.
    """
    clothcategory = db.get(clothcategory_key)

    cloths = []
    num_results = 0

    for cloth in clothcategory.cloths:
      if num_results % PICTURES_PER_ROW == 0:
        current_row = []
        cloths.append(current_row)
      current_row.append(cloth)
      num_results += 1

    self.render_to_response('clothcategory.html', {
        'num_results': num_results,
        'clothcategory_key': clothcategory.key(),
        'cloths': cloths,
        'clothcategory_name': clothcategory.name
      })


class ImageSharingUploadCloth(ImageSharingBaseHandler):
  """Handler for uploading images."""

  def get(self, clothcategory_key):
    """Display the image upload form.

    Args:
      clothcategory_key: datastore key for the clothcategory to upload the image to
    """
    clothcategory = db.get(clothcategory_key)
    self.render_to_response('upload.html', {
        'clothcategory_key': clothcategory.key(),
        'clothcategory_name': clothcategory.name
      })

  def post(self, clothcategory_key):
    """Process the image upload form.

    We also generate the thumbnail for the cloth at this point.

    Args:
      clothcategory_key: datastore key for the clothcategory to add the image to
    """
    clothcategory = db.get(clothcategory_key)
    if clothcategory is None:
      self.error(400)
      self.response.out.write('Couldn\'t find specified clothcategory')

    title = cgi.escape(self.request.get('title'))
    caption = cgi.escape(self.request.get('caption'))
    tags = cgi.escape(self.request.get('tags')).split(',')
    tags = [tag.strip() for tag in tags]
    # Get the actual data for the cloth
    img_data = self.request.POST.get('clothfile').file.read()

    try:
      img = images.Image(img_data)
      # Basically, we just want to make sure it's a PNG
      # since we don't have a good way to determine image type
      # through the API, but the API throws an exception
      # if you don't do any transforms, so go ahead and use im_feeling_lucky.
      img.im_feeling_lucky()
      png_data = img.execute_transforms(images.PNG)

      img.resize(60, 100)
      thumbnail_data = img.execute_transforms(images.PNG)

      Cloth(submitter=users.get_current_user(),
              title=title,
              caption=caption,
              clothcategory=clothcategory,
              tags=tags,
              data=png_data,
              thumbnail_data=thumbnail_data).put()

      self.redirect('/clothcategory/%s' % clothcategory.key())
    except images.BadImageError:
      self.error(400)
      self.response.out.write(
          'Sorry, we had a problem processing the image provided.')
    except images.NotImageError:
      self.error(400)
      self.response.out.write(
          'Sorry, we don\'t recognize that image format.'
          'We can process JPEG, GIF, PNG, BMP, TIFF, and ICO files.')
    except images.LargeImageError:
      self.error(400)
      self.response.out.write(
          'Sorry, the image provided was too large for us to process.')

class ImageSharingShowCloth(ImageSharingBaseHandler):
  """Handler for viewing a single image.

  Note that this doesn't actually serve the cloth, only the page
  containing it. That happens in ImageSharingServeImage.
  """

  def get(self, cloth_key):
    """Renders the page for a single cloth.

    Args:
      cloth_key: key for the Cloth model for the cloth to display
    """

    cloth = db.get(cloth_key)
    self.render_to_response('show_cloth.html', {
        'cloth': cloth,
        'cloth_key': cloth.key(),
    })


class ImageSharingServeCloth(webapp.RequestHandler):
  """Handler for dynamically serving an cloth from the datastore.

  Very simple - it just pulls the appropriate data out of the datastore
  and serves it.
  """

  def get(self, display_type, cloth_key):
    """Dynamically serves a PNG image from the datastore.

    Args:
      type: a string describing the type of image to serve (image or thumbnail)
      cloth_key: the key for a Cloth model that holds the image
    """
    image = db.get(cloth_key)

    if display_type == 'cloth':
      self.response.headers['Content-Type'] = 'image/png'
      self.response.out.write(image.data)
    elif display_type == 'thumbnail':
      self.response.headers['Content-Type'] = 'image/png'
      self.response.out.write(image.thumbnail_data)
    else:
      self.error(500)
      self.response.out.write(
          'Couldn\'t determine what type of image to serve.')

class ImageSharingSearch(ImageSharingBaseHandler):
  """Handler for searching cloths by tag."""

  def get(self):
    """Displays the tag search box and possibly a list of results."""
    query = cgi.escape(self.request.get('q'))
    cloths = []
    if query:
      # ListProperty magically does want we want: search for the occurrence
      # of the term in any of the tags.
      cloths = Cloth.all().filter('tags =', query)
    else:
      query = ''
    self.render_to_response('search.html', {
        'query': query,
        'cloths': cloths,
      })


def main():
  url_map = [('/', ImageSharingClothCategoryIndex),
             ('/new', ImageSharingClothCategoryCreate),
             ('/clothcategory/([-\w]+)', ImageSharingClothCategoryView),
             ('/upload/([-\w]+)', ImageSharingUploadCloth),
             ('/show_cloth/([-\w]+)', ImageSharingShowCloth),
             ('/(thumbnail|cloth)/([-\w]+)', ImageSharingServeCloth),
             ('/search', ImageSharingSearch)]
  application = webapp.WSGIApplication(url_map,
                                       debug=True)
  wsgiref.handlers.CGIHandler().run(application)

if __name__ == '__main__':
  main()
